import os
import pandas as pd
from flask import request, send_from_directory
from dash import get_app
from dash import (
    dcc,
    html,
    callback,
    Output,
    Input,
    State,
    register_page,
)
from ddbtools import BaseCRUD, DBDf, Filter, Comparator
from gypb.db import get_db, get_ddbapi,get_rqapi
from gypb.component import DFTable
from gypb.apps.performance_analysis import industry_concentration, riskfactor_exposure
from xq_factor import get_option, set_option, add_api,Config
from xq_factor.factor import LeafFactor, ObjectedLeafFactor, SingleLeafFactor
from xq_factor.operator import PROPORTION, RANK,DIFF,REF
import feffery_antd_components as fac
from xqdata.constant import Frequency
from xqdata_rq.func import _all_instruments
from xqdata_rq import RQDataApi
from xqdata_ddb import DDBDataApi
from xqdata.schemas import SecuType
import empyrical

register_page(__name__, name="业绩归因", order=8)

ddbapi = get_ddbapi()
rqapi = get_rqapi()
add_api(ddbapi)
add_api(rqapi)
set_option("frequency", Frequency.DAILY)

layout = html.Div(
    [
        fac.AntdSelect(
            id="perfanalysis-select-product",
            placeholder="请选择产品",
            options=[],
            autoSpin=True,
            debounceWait=800,
            style={"width": "600px"},
        ),
        fac.AntdSelect(
            id="perfanalysis-select-benchmark",
            placeholder="请选择对标基准",
            options=[
                {"value": "上证指数", "label": "上证指数"},
                {"value": "沪深300", "label": "沪深300"},
                {"value": "中证500", "label": "中证500"},
                {"value": "中证1000", "label": "中证1000"},
            ],
            value="沪深300",
            autoSpin=True,
            debounceWait=800,
            style={"width": "600px"},
        ),
        fac.AntdDateRangePicker(
            id="perfanalysis-date-range-picker",
            placeholder=["分析起始日期", "分析结束日期"],
        ),
        fac.AntdDivider(),
        fac.AntdButton("计算归因", id="button-performance-analysis", type="primary"),
        fac.AntdDivider(),
        fac.AntdTitle("基础指标", level=3),
        html.Div(
            id="performance-analysis-basic-indicator",
        ),
        fac.AntdDivider(),
        fac.AntdTitle("持仓分析", level=3),
        html.Div(
            id="performance-analysis-holdings",
        ),
    ]
)


@callback(
    Output("performance-analysis-basic-indicator", "children"),
    Input("button-performance-analysis", "nClicks"),
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def basic_indicator(nClicks, prodcode: str, benchmark, date_range):
    if nClicks:
        code, name = prodcode.split("-")
        start, end = pd.to_datetime(date_range)
        set_option("universe", code)
        set_option("start_time", start)
        set_option("end_time", end)     
        # 产品净值
        netvalue = LeafFactor("netvalue", code)
        cum_netvalue = LeafFactor("cum_netvalue", code)
        returns = DIFF(cum_netvalue) / REF(netvalue)
        # 期间收益
        empyrical.cum_returns_final(returns.value)


@callback(
    Output("performance-analysis-holdings", "children"),
    Input("button-performance-analysis", "nClicks"),
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def render_table(nClicks, prodcode: str, benchmark, date_range):
    if nClicks:
        code, name = prodcode.split("-")
        start, end = pd.to_datetime(date_range)
        benchmark_map = {
            "上证指数": "000001.SSE",
            "沪深300": "000300.SSE",
            "中证500": "000905.SSE",
            "中证1000": "000852.SSE",
        }
        benchmark = benchmark_map[benchmark]
        crud = BaseCRUD("dfs://infos", "prodcode_account")
        with get_db() as session:
            prodcode_account_infodf = crud.get(
                session, conds=[Filter("prod_code", Comparator.eq, code)]
            )
            # if len(prodcode_account_infodf) == 0:
            #     return fac.AntdAlert(
            #         message="该产品不存在", type="error", closable=True
            # )
        market_value = sum(
            [
                ObjectedLeafFactor("market_value", account)
                for account in prodcode_account_infodf["account"].to_list()
            ]
        )
        citics_2019_l1_name = LeafFactor("citics_2019_l1_name", api="rq")
        constituent_weight = ObjectedLeafFactor(
            "constituent_weight", benchmark, api="rq"
        )

        set_option("start_time", start)
        set_option("end_time", end)

        risk_factors = [
            LeafFactor(factor, api="rq")
            for factor in [
                "beta",
                "momentum",
                "size",
                "book_to_price",
                "non_linear_size",
                "earnings_yield",
                "residual_volatility",
                "growth",
                "leverage",
                "liquidity",
            ]
        ]
        # momentum = LeafFactor("momentum", api="rq")
        # beta = LeafFactor("beta", api="rq")
        # book_to_price = LeafFactor("book_to_price", api="rq")

        with get_db() as session:
            ddb_api = DDBDataApi()
            ddb_api.session = session
            add_api(ddb_api)
            # 持仓行业集中度
            ind_data = industry_concentration(market_value, citics_2019_l1_name)
            ind_data_percentage = (ind_data * 100).applymap(
                lambda x: f"{x:.2f}%" if not pd.isna(x) else x
            )
            ind_data_percentage.reset_index(inplace=True)
            # 对标基准行业集中度
            constituent_weight_data = industry_concentration(
                constituent_weight, citics_2019_l1_name
            )
            constituent_weight_data_percentage = (
                constituent_weight_data * 100
            ).applymap(lambda x: f"{x:.2f}%" if not pd.isna(x) else x)
            constituent_weight_data_percentage.reset_index(inplace=True)

            # 持仓风格因子暴露
            riskfactor_data = riskfactor_exposure(market_value, risk_factors)
            riskfactor_data_rounded = riskfactor_data.applymap(
                lambda x: f"{x:.2f}" if not pd.isna(x) else x
            )
            riskfactor_data_rounded.reset_index(inplace=True)
            # 对标基准风格因子暴露
            benchmark_riskfactor_data = riskfactor_exposure(
                constituent_weight, risk_factors
            )
            benchmark_riskfactor_data_rounded = benchmark_riskfactor_data.applymap(
                lambda x: f"{x:.2f}" if not pd.isna(x) else x
            )
            benchmark_riskfactor_data_rounded.reset_index(inplace=True)
            return [
                DFTable(
                    ind_data_percentage,
                    name="industry_concentration",
                    downloadable=True,
                ).layout,
                DFTable(
                    constituent_weight_data_percentage,
                    name="benchmark_industry_concentration",
                    downloadable=True,
                ).layout,
                DFTable(
                    riskfactor_data_rounded,
                    name="riskfactor_exposure",
                    downloadable=True,
                ).layout,
                DFTable(
                    benchmark_riskfactor_data_rounded,
                    name="benchmark_riskfactor_exposure",
                    downloadable=True,
                ).layout,
            ]


@callback(
    Output("perfanalysis-select-product", "options"),
    Input(
        "perfanalysis-select-product",
        "debounceSearchValue",
    ),
)
def select_product(debounceSearchValue):
    if debounceSearchValue:
        crud = BaseCRUD("dfs://infos", "prodcode_account")
        with get_db() as session:
            prods = crud.get(
                session,
                conds=[Filter("prod_name", Comparator.like, debounceSearchValue)],
            )
        prods = prods[["prod_code", "prod_name"]].drop_duplicates()
        options = [
            {
                "value": f"{prod.get('prod_code')}-{prod.get('prod_name')}",
                "label": f"{prod.get('prod_code')}-{prod.get('prod_name')}",
            }
            for prod in prods.to_dict("records")[:20]
        ]
        return options


stocks = _all_instruments(type=SecuType.STOCK)
set_option("universe", stocks.index)
